/* Filename: server.c
 * No.144-147.tcp 动态进程池案例实现1-4
 * Description:
 *   server进程池：至少5个空闲，最多10个空闲，最多总数20(.是闲,x是忙,-是无)
 *   $ ./server
 *   #1 while true; do (./client 127.0.0.1 &); sleep 1; done
 *   #2 while true; do (./client 127.0.0.1 &); sleep 1; done
 *   #3 while true; do (./client 127.0.0.1 &); sleep 1; done
 *   #4 while true; do (./client 127.0.0.1 &); sleep 1; done
 * Last modified: humble 2020-09-23 13:00:29
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/times.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "proto.h"

#define MINSPARESERVER (5)
#define MAXSPARESERVER (10)
#define MAXCLIENTS (20)

#define SIG_NOTIFY (SIGUSR2)
#define IPSTRSIZE (40)
#define LINEBUFSIZE (80)

typedef enum{
    STATE_IDLE = 0,
    STATE_BUSY
}state_t;

struct server_st {
    pid_t pid;
    state_t state;
    //int reuse; //子进程重复使用次数，如果过多父就把它kill掉，再创建新的，减少出现bug概率(kill之前确保子进程当前不在处理任务)
};

static struct server_st *serverpool;
static int idle_count = 0, busy_count = 0;
static int sd;

static void usage(void);

static void usr2_handler(int s){ return ; }

static void server_job(int pos)
{
    int ppid = getppid();
    struct sockaddr_in raddr;
    socklen_t raddr_len;
    int client_sd;
    char ipstr[IPSTRSIZE] = {0};
    char linebuf[LINEBUFSIZE] = {0};
    time_t stamp;
    int len;

    while(1){
        serverpool[pos].state = STATE_IDLE;
        kill(ppid, SIG_NOTIFY);
        client_sd = accept(sd, (void *)&raddr, &raddr_len);
        if(client_sd < 0){
            if(errno != EINTR || errno != EAGAIN){ perror("accept()"); exit(1); } //真错
            else { continue; } //假错
        }

        serverpool[pos].state = STATE_BUSY;
        kill(ppid, SIG_NOTIFY);
        inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
        //printf("[%d]client:%s:%d\n", getpid(), ipstr, ntohs(raddr.sin_port));

        stamp = times(NULL);
        len = snprintf(linebuf, LINEBUFSIZE, FMT_STAMP, stamp);
        send(client_sd, linebuf, len, 0);
        /* if error */
        sleep(5);
        close(client_sd);
    }
}

static int scan_poll(void)
{
    int i, busy = 0, idle = 0;
    for(i = 0; i < MAXCLIENTS; i++)
    {
        if(serverpool[i].pid == -1){ continue; }
        if(kill(serverpool[i].pid, 0)){ serverpool[i].pid = -1; continue; }
        if(serverpool[i].state == STATE_IDLE){ idle++; }
        else if(serverpool[i].state == STATE_BUSY){ busy++; }
        else{ fprintf(stderr, "Unknown state.\n"); /*_exit(1);*/ abort(); }
    }
    idle_count = idle;
    busy_count = busy;
    return 0;
}

static int add_1_server(void)
{
    int slot;
    pid_t pid;

    if(idle_count + busy_count >= MAXCLIENTS){ return -1; }

    for(slot = 0; slot < MAXCLIENTS; slot++)//找空位
    {
        if(serverpool[slot].pid == -1){ break; }
    }
    serverpool[slot].state = STATE_IDLE;

    pid = fork();
    if(pid < 0){ perror("fork()"); exit(1); }
    if(pid == 0){ server_job(slot); exit(0); }
    else{ serverpool[slot].pid = pid; idle_count++; }
    return 0;
}

static int del_1_server(void)
{
    int i;
    if(idle_count == 0){ return -1; }
    for(i = 0; i < MAXCLIENTS; i++){
        if(serverpool[i].pid != -1 && serverpool[i].state == STATE_IDLE){
            kill(serverpool[i].pid, SIGTERM);
            serverpool[i].pid = -1;
            idle_count--;
            break;
        }
    }
    return 0;
}

int main(int argc, char **argv)
{
    int i;
    struct sigaction sa, osa;
    int val = 1;
    struct sockaddr_in laddr;
    sigset_t set, oset;

    if(argc != 1){ fprintf(stderr, "argc !=1"); usage(); exit(1); }

    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_NOCLDWAIT; //阻止子进程变成僵尸状态，免去主进程收僵尸(wait)动作，即:子进程自行消亡
    sigaction(SIGCHLD, &sa, &osa);//子进程退出后，父会收到SIGCHLD信号

    sa.sa_handler = usr2_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIG_NOTIFY, &sa, &osa);

    sigemptyset(&set);
    sigaddset(&set, SIG_NOTIFY);
    sigprocmask(SIG_BLOCK, &set, &oset);

    serverpool = mmap(NULL, sizeof(struct server_st) * MAXCLIENTS, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); //malloc();
    if(serverpool == MAP_FAILED){ perror("mmap()"); exit(1); }

    for(i = 0; i < MAXCLIENTS; i++) { serverpool[i].pid = -1; } //reset

    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd < 0){ perror("socket()"); exit(1); }

    if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0){ perror("setsockopt()"); exit(1); }

    laddr.sin_family = AF_INET;
    laddr.sin_port = htons(atoi(SERVERPORT));
    inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);
    if(bind(sd, (void *)&laddr, sizeof(laddr)) < 0){ perror("bind()"); exit(1); }

    if(listen(sd, 100) < 0){ perror("listen()"); exit(1); }

    for(i = 0; i < MINSPARESERVER; i++) { add_1_server(); } //init

    while(1){
        sigsuspend(&oset);

        scan_poll();

        //control the poll
        if(idle_count > MAXSPARESERVER){ //空闲太多
            for(i = 0; i < (idle_count - MAXSPARESERVER); i++){ del_1_server(); }
        }
        else if(idle_count < MINSPARESERVER){ //空闲太少
            for(i = 0; i < (MINSPARESERVER - idle_count); i++){ add_1_server(); }
        }

        //printf the poll
        for(i = 0; i < MAXCLIENTS; i++){
            if(serverpool[i].pid == -1){ putchar('-'); }
            else if(serverpool[i].state == STATE_IDLE){ putchar('.'); }
            else { putchar('x'); }
        }
        putchar('\n');
    }

    sigprocmask(SIG_SETMASK, &oset, NULL);
    return 0;
}

static void usage(void)
{
#define USAGE "Usage:\n\
  Helloworld\n"
    printf(USAGE);
}
